﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections;

namespace PerformanceTimers
{
    class TrackerInstance
    {
        public struct TrackerGroup
        {
            public List<TrackerInstance> Roots;
            public Stack<TrackerInstance> Stack;
        }

        private static Hashtable<Thread, TrackerGroup> trackers;
        public static Hashtable<Thread, TrackerGroup> ThreadGroups
        {
            get
            {
                if (trackers == null)
                {
                    var instance = new Hashtable<Thread, TrackerGroup>(Environment.ProcessorCount);
                    Interlocked.CompareExchange(ref trackers, instance, null);
                }

                return trackers;
            }
        }

        private static TimeSpan updateFrequency = TimeSpan.FromSeconds(0.5);
        public static TimeSpan UpdateFrequency
        {
            get { return updateFrequency; }
            set { updateFrequency = value; }
        }

        private static DateTime lastUpdated;

        public static bool Update()
        {
            if (UpdateFrequency.Ticks <= 0)
                throw new InvalidOperationException("UpdateFrequency cannot be less than or equal to 0.");

            var now = DateTime.Now;
            var dt = now - lastUpdated;

            if (dt < UpdateFrequency)
                return false;

            foreach (var group in ThreadGroups)
            {
                UpdateInstances(group.Value.Roots, dt.Ticks);
            }

            lastUpdated = now;
            return true;
        }

        private static void UpdateInstances(List<TrackerInstance> instances, long dt)
        {
            for (int i = 0; i < instances.Count; i++)
            {
                var instance = instances[i];
                var totalTime = Interlocked.Exchange(ref instance.TotalTime, 0);
                var usage = totalTime / (float)dt * 100;
                instance.ThreadUsage = (instance.ThreadUsage + usage) / 2f;

                UpdateInstances(instance.Children, dt);
            }
        }


        public PerformanceTracker Tracker { get; private set; }
        public List<TrackerInstance> Children { get; private set; }
        public float ThreadUsage { get; private set; }
        public TrackerGroup Group { get; set; }
        public DateTime StartTime { get; set; }
        public int TotalTime;

        public TrackerInstance(PerformanceTracker tracker)
        {
            this.Tracker = tracker;
            this.Children = new List<TrackerInstance>();
        }
    }

    /// <summary>
    /// A performance timer.
    /// </summary>
    public class PerformanceTracker
    {
        static List<PerformanceTracker> trackers;
        internal static List<PerformanceTracker> Trackers
        {
            get
            {
                if (trackers == null)
                {
                    var instance = new List<PerformanceTracker>();
                    Interlocked.CompareExchange(ref trackers, instance, null);
                }

                return trackers;
            }
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the average duration.
        /// </summary>
        /// <value>The average duration.</value>
        public TimeSpan AverageDuration
        {
            get
            {
                var now = DateTime.Now;
                if (now - averageLastAcessed > TrackerInstance.UpdateFrequency)
                {
                    visibleAverageDuration = averageDuration;
                    averageLastAcessed = now;
                }

                return visibleAverageDuration;
            }
        }
        
        Predicate<TrackerInstance> findThis;
        Hashtable<Thread, TrackerInstance> activeTrackers;
        TimeSpan averageDuration;
        TimeSpan visibleAverageDuration;
        DateTime averageLastAcessed;

        /// <summary>
        /// Initializes a new instance of the <see cref="PerformanceTracker"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        public PerformanceTracker(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("Cannot be null or empty.", "name");

            this.Name = name;
            this.activeTrackers = new Hashtable<Thread, TrackerInstance>(Environment.ProcessorCount);
            this.findThis = (t => t.Tracker == this);

            lock (Trackers)
                Trackers.Add(this);
        }

        /// <summary>
        /// Starts the timer.
        /// </summary>
        public void Start()
        {
            var currentThread = Thread.CurrentThread;
            var instance = GetInstance(currentThread);
            activeTrackers.UnsafeSet(currentThread, instance);

            instance.StartTime = DateTime.Now;
        }

        private TrackerInstance GetInstance(Thread currentThread)
        {
            TrackerInstance.TrackerGroup group;
            if (!TrackerInstance.ThreadGroups.TryGet(currentThread, out group))
            {
                group = new TrackerInstance.TrackerGroup()
                {
                    Roots = new List<TrackerInstance>(),
                    Stack = new Stack<TrackerInstance>()
                };

                TrackerInstance.ThreadGroups.UnsafeSet(currentThread, group);
            }

            List<TrackerInstance> parentList = null;
            if (group.Stack.Count > 0)
                parentList = group.Stack.Peek().Children;
            else
                parentList = group.Roots;

            TrackerInstance instance = parentList.Find(findThis);
            if (instance == null)
            {
                instance = new TrackerInstance(this);
                instance.Group = group;
                parentList.Add(instance);
            }

            group.Stack.Push(instance);

            return instance;
        }

        /// <summary>
        /// Stops the timer.
        /// </summary>
        /// <returns>Returns the time elapsed in between the Start and Stop calls.</returns>
        public TimeSpan Stop()
        {            
            var endTime = DateTime.Now;

            TrackerInstance instance = null;
            if (!activeTrackers.TryGet(Thread.CurrentThread, out instance))
                throw new InvalidOperationException("Stop cannot be called before Start on this thread.");
            activeTrackers.Add(Thread.CurrentThread, null);

            var duration = (int)(endTime.Ticks - instance.StartTime.Ticks);
            //Interlocked.Add(ref instance.TotalTime, duration);
            int oldValue;
            int newValue;
            do
            {
                oldValue = instance.TotalTime;
                newValue = oldValue + duration;
            } while (Interlocked.CompareExchange(ref instance.TotalTime, newValue, oldValue) != oldValue);

            if (averageDuration == TimeSpan.Zero)
                averageDuration = new TimeSpan(duration);
            else
                averageDuration = new TimeSpan((averageDuration.Ticks + duration) / 2);

            instance.Group.Stack.Pop();

            return new TimeSpan(duration);
        }
    }
}
